/*
* Copyright 2011 Digi-Data Corporation
*/
package com.digidata.util

import org.apache.log4j.*
import org.codehaus.groovy.runtime.StackTraceUtils;
 
/**
 * Provides dynamic logging using the log4j package.  Leverages the dynamic 
 * capabilities of Groovy to enhance log4j.  Implicitly determines the class 
 * from which the Log call is being made.
 * 
 * <pre>
 * Example:
 * 
 *   class foo{
 *     def bar(){
 *       Log.debug("Test log entry.")
 *     }
 *   }
 * </pre> 	
 * Creates an debug level log entry for the foo class when bar is called. 
 * <br>
 *  
 * @author geoff.martin
 * 
 */
class Log {
	
	/**
	 * The string format to use in the log output
	 */
	public static String PATTERN = "%d{ABSOLUTE} %-5p [%c{1}] %m%n"
	/**
	 * The current default level for logging if none is specified for the root logger.
	 */
	public static Level LEVEL = Level.INFO
	/**
	 * The default level to use if none is specified by for a class logger.
	 */
	public static Level _defaultLevel = Level.INFO
	/**
	 * Flag to determine if the logger has been configured yet.
	 */
	private static boolean initialized = false
	/**
	 * Map of classes and the logging level they are set to.
	 */
	private static Map LocalLevels = [:]
	 
	/**
	 * Method to get the logger.
	 * @return
	 */
	private static Logger logger() {
		def targetClass = findCallingClass(Thread.currentThread().stackTrace)
		if(Logger.getLogger(targetClass).level == null)
			setLocalLevel(targetClass)
	    //if (!initialized) { basic() }
	    return Logger.getLogger(targetClass)
	}
	
	/**
	 * Check if the logger for the specified class has a level set.
	 * @param clazz Class to check the level for.
	 * @return boolean
	 */
	static private Boolean isLocalLevelSet(String clazz){
		return LocalLevels.containsKey(clazz)
	}
	
	/**
	 * Set the local level for a class.
	 * @param clazz The class to set the level for.
	 */
	static private setLocalLevel(String clazz){
		if(isLocalLevelSet(clazz)){	return }
		else
			LocalLevels.put(clazz, _defaultLevel)
		
		Logger.getLogger(clazz).setLevel(_defaultLevel)
	}
	
	/**
	* Set the local level for the calling class.
	* @param level The level to set for the calling class.
	*/
	static public setLocalLevel(Level level){
		def targetClass = findCallingClass(Thread.currentThread().stackTrace)
		
		if(isLocalLevelSet(targetClass)){
			LocalLevels."$targetClass" = level
		}
		else
			LocalLevels.put(targetClass, level)
		
		Logger.getLogger(targetClass).setLevel(level)
	}
	
	/**
	 * Set the level of the root logger.
	 * @param newLevel Level to change the root logger to.
	 */
	static setRootLevel(Level newLevel) {
		LEVEL = newLevel
		Logger.getRootLogger().setLevel(newLevel)
	}
	
	/**
	 * Method to record a debug level log entry.
	 * @param messages Log entry to make.
	 */
	static debug(Object... messages) { log(Level.DEBUG, null, messages) }
	/**
	 * Method to record a debug level log entry.
	 * @param t Throwable for this entry.
	 * @param messages Log entry to make.
	 */
	static debug(Throwable t, Object... messages) { log(Level.DEBUG, t, messages) }

	/**
	* Method to record an info level log entry.
	* @param messages Log entry to make.
	*/
	static info(Object... messages) { log(Level.INFO, null, messages) }
	/**
	* Method to record an info level log entry.
	* @param t Throwable for this entry.
	* @param messages Log entry to make.
	*/
	static info(Throwable t, Object... messages) { log(Level.INFO, t, messages) }
	
	/**
	* Method to record a warn level log entry.
	* @param messages Log entry to make.
	*/
	static warn(Object... messages) { log(Level.WARN, null, messages) }
	/**
	* Method to record a warn level log entry.
	* @param t Throwable for this entry.
	* @param messages Log entry to make.
	* @return
	*/
	static warn(Throwable t, Object... messages) { log(Level.WARN, t, messages) }
	 
	/**
	* Method to record an error level log entry.
	* @param messages Log entry to make.
	* @return
	*/
	static error(Object... messages) { log(Level.ERROR, null, messages) }
	/**
	* Method to record an error level log entry.
	* @param t Throwable for this entry.
	* @param messages Log entry to make.
	* @return
	*/
	static error(Throwable t, Object... messages) { log(Level.ERROR, t, messages) }

	 /**
	  * Main logging call for all levels.
	  * @param level Level of log entry to make.
	  * @param t Throwable type for this message.
	  * @param messages Message to log.
	  * @return
	  */
	private static log(Level level, Throwable t, Object... messages) {
		if (messages) {
			def levelFunc = level.toString().toLowerCase()
			def log = logger()
			
			log."$levelFunc"(messages.join(), t)
			//println LocalLevels 
			//println "log info: " + log.name + "   " + log.getLevel()
			/*(if (level.equals("Warn") || level.equals("Error") || log."is${levelFunc}Enabled" ()) {
				log."${level.toLowerCase()}" (messages.join(), t)
			}*/
		}
	}
	
	/**
	 * Method to determine what the calling class was.
	 * @param stack The StackTrace to search.
	 * @return String representation of the calling class.
	 */
	private static String findCallingClass(StackTraceElement[] stack){
		
		for(def count = 0; count < stack.size(); count++){
			if((stack[count].lineNumber > 0)&&
				(!stack[count].fileName.endsWith(".java"))&&
				(!stack[count].fileName.equals("Log.groovy"))){
				return stack[count].className
			}
		}
	}
	
	/**
	 * Method to set a basic logging configuration.
	 */
	static basic() {
		def simple = new PatternLayout(PATTERN)
		BasicConfigurator.configure(new ConsoleAppender(simple))
		Logger.getRootLogger().setLevel(LEVEL)
		LocalLevels.each{
			Logger.getLogger(it.key).setLevel(it.value)
		}
		
		initialized = true
	}
}
